/* ===== semantic.c ===== */
#include <string>
#include <iostream>
#include <map>
#include <list>
#include <vector>


using namespace std;

#include <stdio.h>
#include <stdlib.h>
#include "ptype.hh"
#include "symtab.hh"
#include "util.hh"

#include "myASTnode.hh"

#include "semantic.hh"

// feedback the main program with our error status
int TypeError = 0;

bool debug = false;
//if (debug) { cout <<  << endl;}//FIXME

int strtoi(string s)
{
    return atoi(s.c_str());
}


/// ---------- Error reporting routines --------------

void errornumparam(int l)
{
    TypeError = 1;
    cout << "L. " << l << ": The number of parameters in the call do not match." << endl;
}

void errorincompatibleparam(int l, int n)
{
    TypeError = 1;
    cout << "L. " << l << ": Parameter " << n << " with incompatible types." << endl;
}

void errorreferenceableparam(int l, int n)
{
    TypeError = 1;
    cout << "L. " << l << ": Parameter " << n << " is expected to be referenceable but it is not." << endl;
}

void errordeclaredident(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Identifier " << s << " already declared." << endl;
}

void errornondeclaredident(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Identifier " << s << " is undeclared." << endl;
}

void errornonreferenceableleft(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Left expression of assignment is not referenceable." << endl;
}

void errorincompatibleassignment(int l)
{
    TypeError = 1;
    cout << "L. " << l << ": Assignment with incompatible types." << endl;
}

void errorbooleanrequired(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Instruction " << s << " requires a boolean condition." << endl;
}

void errorisnotprocedure(int l)
{
    TypeError = 1;
    cout << "L. " << l << ": Operator ( must be applied to a procedure in an instruction." << endl;
}

void errorisnotfunction(int l)
{
    TypeError = 1;
    cout << "L. " << l << ": Operator ( must be applied to a function in an expression." << endl;
}

void errorincompatibleoperator(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Operator " << s << " with incompatible types." << endl;
}

void errorincompatiblereturn(int l)
{
    TypeError = 1;
    cout << "L. " << l << ": Return with incompatible type." << endl;
}

void errorreadwriterequirebasic(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Basic type required in " << s << "." << endl;
}

void errornonreferenceableexpression(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Referenceable expression required in " << s << "." << endl;
}

void errornonfielddefined(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Field " << s << " is not defined in the struct." << endl;
}

void errorfielddefined(int l, string s)
{
    TypeError = 1;
    cout << "L. " << l << ": Field " << s << " already defined in the struct." << endl;
}

/// ------------------------------------------------------------
/// Table to store information about program identifiers
symtab symboltable;

static void InsertintoST(int line, string kind, string id, ptype tp)
{
    infosym p;

    if (symboltable.find(id) && symboltable.jumped_scopes(id) == 0)
        errordeclaredident(line, id);
    else
    {
        symboltable.createsymbol(id);
        symboltable[id].kind = kind;
        symboltable[id].tp = tp;
    }
}

/// ------------------------------------------------------------

bool isbasickind(string kind)
{
    return kind == "int" || kind == "bool" || kind == "string";
}

void construct_struct(AST * a)
{
    AST *ident = child(a, 0);
    a->tp = create_type("struct", 0, 0);
    while (ident != 0)
    {
        AST *type = child(ident, 0);
        TypeCheck(type);
        if (a->tp->struct_field.find(ident->text) == a->tp->struct_field.end())
        {
            a->tp->struct_field[ident->text] = type->tp;
            a->tp->ids.push_back(ident->text);
        }
        else
            errorfielddefined(ident->line, ident->text);
        ident = ident->right;
    }
}

void construct_array(AST * a)
{
    AST *size = child(a, 0);
    AST *type = child(a, 1);
    TypeCheck(size);
    TypeCheck(type);
    if (size->tp->kind != "error" && size->tp->kind != "int")
        errorincompatibleassignment(a->line);
    a->tp = create_type("array", 0, 0);
    a->tp->numelemsarray = stringtoint(size->text);
    a->tp->down = type->tp;
}

void insert_vars(AST *var_list)
{
    AST *var = child(var_list, 0);
    while (var != 0)
    {
        AST *type = child(var, 0);
        TypeCheck(type);
        InsertintoST(var->line, "idvarlocal", var->text, type->tp);
        var = var->right;
    }
}

void insert_headers(AST *block_list)
{
    AST *block = child(block_list, 0);
    while (block != 0) {
        AST *header = child(block, 0);
        AST *param_list = child(header, 0);
        AST *return_type = child(header, 1);
        // Create ptype for the procedure
        header->tp = create_type(block->kind, 0, 0);
        header->tp->numelemsarray = 0;  // Count number of parameters
        // Create ptype for each parameter
        AST *prev = 0;
        AST *param = child(param_list, 0);
        // Iterate right until all the parameters are visited
        while (param != 0)
        {
            // Check and create ptype for the current parameter
            AST *ident = child(param, 0);
            AST *type = child(param, 1);
            TypeCheck(type);
            param->tp = create_type("par" + param->kind, 0, 0);
            param->tp->down = type->tp;
            // For the first parameter assign to down of the list
            if (prev == 0)
                header->tp->down = param->tp;
            // For the rest append to the right of the previous parameter
            else
                prev->tp->right = param->tp;
            // Increment parameter count and move to the right
            header->tp->numelemsarray++;
            prev = param;
            param = param->right;
        }
        if (return_type != 0)
        {
            TypeCheck(return_type);
            header->tp->right = return_type->tp;
        }
        if (block->kind == "procedure")
            InsertintoST(header->line, "idproc", header->text, header->tp);
        else if (block->kind == "function")
            InsertintoST(header->line, "idfunc", header->text, header->tp);
        // Move to next header
        block = block->right;
    }
}

void insert_params(AST *param_list)
{
    AST *param = child(param_list, 0);
    while (param != 0)
    {
        AST *ident = child(param, 0);
        AST *type = child(param, 1);
        // TP must have been created when the header was inserted
        InsertintoST(param->line, "idpar" + param->kind, ident->text, type->tp);
        param = param->right;
    }
}

void check_params(ptype header_tp, AST *param_list, int line)
{
    int count = 0;
    for (AST *param = child(param_list, 0); param != 0; param = param->right)
        count++;
    if (count != header_tp->numelemsarray)
        errornumparam(line);
    else
    {
        count = 1;
        AST *param = child(param_list, 0);
        ptype param_tp = header_tp->down;
        //if (debug) { cout << "checking params " << header_tp->kind; if (header_tp->kind == "function") { cout << " returns " << header_tp->right->kind; } cout << endl; }//FIXME
        while (param != 0)
        {
            TypeCheck(param, "expression");
            //if (debug) { cout << "   parameter " << count << " " << param_tp->down->kind << "? " << param->kind << ":" << param->tp->kind << endl; }//FIXME
            if (param_tp->kind == "parref" &&
                (!param->ref || param->tp->kind == "procedure" || param->tp->kind == "function"))
            {
                errorreferenceableparam(param->line, count);
            }
            if (param->tp->kind != "error" &&
                param_tp->down->kind != "error" &&
                !equivalent_types(param->tp, param_tp->down))
            {
                errorincompatibleparam(param->line, count);
            }
            param_tp = param_tp->right;
            param = param->right;
            count++;
        }
    }
}

void TypeCheck(AST * a, string info)
{
    if (!a)
    {
        return;
    }
    //cout<<"Starting with node \""<<a->kind<<"\""<<endl;
    if (a->kind == "list")
    {
        // At this point only instruction, procedures or parameters lists are possible.
        for (AST * c = a->down; c != 0; c = c->right)
            TypeCheck(c, info);
    }
    else if (a->kind == "intconst")
    {
        a->tp = create_type("int", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "true" || a->kind == "false")
    {
        a->tp = create_type("bool", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "+" || a->kind == "/" || 
            (a->kind == "-" && child(a, 1) != 0))
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "int") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "int"))
        {
            errorincompatibleoperator(a->line, a->kind);
        }
        a->tp = create_type("int", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "*")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        a->tp = create_type("error", 0, 0);
        if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "int") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "int"))
        {
            /* examen */
            if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "array") ||
                (rexpr->tp->kind != "error" && rexpr->tp->kind != "array"))
            { 
                errorincompatibleoperator(a->line, a->kind);
            }
            else if (lexpr->tp->kind == "array" && rexpr->tp->kind == "array")
            {
                if ((lexpr->tp->numelemsarray != rexpr->tp->numelemsarray) ||
                    (lexpr->tp->down->kind != "error" && lexpr->tp->down->kind != "int") ||
                    (rexpr->tp->down->kind != "error" && rexpr->tp->down->kind != "int"))
                {
                    errorincompatibleoperator(a->line, a->kind);
                }
            }
            /* fiexamen */
        }
        a->tp = create_type("int", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "-")
    {
        AST *lexpr = child(a, 0);
        TypeCheck(lexpr, "expression");
        if (lexpr->tp->kind != "error" && lexpr->tp->kind != "int")
            errorincompatibleoperator(a->line, a->kind);
        a->tp = create_type("int", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "<" || a->kind == ">")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "int") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "int"))
        {
            errorincompatibleoperator(a->line, a->kind);
        }
        a->tp = create_type("bool", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "=")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if (((lexpr->tp->kind != "error" && lexpr->tp->kind != "int") ||
             (rexpr->tp->kind != "error" && rexpr->tp->kind != "int")) &&
            ((lexpr->tp->kind != "error" && lexpr->tp->kind != "bool") ||
             (rexpr->tp->kind != "error" && rexpr->tp->kind != "bool")))
        {
            errorincompatibleoperator(a->line, a->kind);
        }
        a->tp = create_type("bool", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "and" || a->kind == "or")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "bool") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "bool"))
        {
            errorincompatibleoperator(a->line, a->kind);
        }
        a->tp = create_type("bool", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "not")
    {
        AST *expr = child(a, 0);
        TypeCheck(expr, "expression");
        if (expr->tp->kind != "error" && expr->tp->kind != "bool")
            errorincompatibleoperator(a->line, a->kind);
        a->tp = create_type("bool", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "ident")
    {
        if (symboltable.find(a->text))
        {
            a->tp = symboltable[a->text].tp;
            if (a->tp->kind != "procedure" && a->tp->kind != "function")
                a->ref = 1;
            else
                a->ref = 0;
        }
        else
        {
            errornondeclaredident(a->line, a->text);
            a->tp = create_type("error", 0, 0);
            a->ref = 0;
        }
    }
    else if (a->kind == ".")
    {
        AST *expr = child(a, 0);
        AST *ident = child(a, 1);
        TypeCheck(expr, "expression");
        if (expr->tp->kind != "error" && expr->tp->kind != "struct")
        {
            errorincompatibleoperator(a->line, "struct.");
            a->tp = create_type("error", 0, 0);
        }
        else if (expr->tp->kind != "error" &&
            expr->tp->struct_field.find(ident->text) == expr->tp->struct_field.end())
        {
            errornonfielddefined(a->line, ident->text);
            a->tp = create_type("error", 0, 0);
        }
        else if (expr->tp->kind == "struct")
            a->tp = expr->tp->struct_field[ident->text];
        else
            a->tp = create_type("error", 0, 0);
        a->ref = expr->ref;
    }
    else if (a->kind == "[")
    {
        AST *expr = child(a, 0);
        AST *index = child(a, 1);
        TypeCheck(expr, "expression");
        TypeCheck(index, "expression");
        if (expr->tp->kind != "error" && expr->tp->kind != "array")
        {
            errorincompatibleoperator(a->line, "array[]");
            a->tp = create_type("error", 0, 0);
        }
        else if (expr->tp->kind == "array")
            a->tp = expr->tp->down;
        else
            a->tp = create_type("error", 0, 0);
        if (index->tp->kind != "error" && index->tp->kind != "int")
            errorincompatibleoperator(a->line, "[]");
        a->ref = expr->ref;
    }
    else if (a->kind == "(")
    {
        AST* ident = child(a, 0);
        AST* param_list = child(a, 1);
        TypeCheck(ident);
        if (ident->tp->kind != "error")
        {
            if (info == "instruction" && ident->tp->kind != "procedure")
                errorisnotprocedure(a->line);
            if (info == "expression" && ident->tp->kind != "function")
                errorisnotfunction(a->line);
            if (ident->tp->kind == "procedure" || ident->tp->kind == "function")
                check_params(ident->tp, param_list, a->line);
            if (ident->tp->kind == "function")
                a->tp = ident->tp->right;
            else
                a->tp = create_type("error", 0, 0);
        }
        else
            a->tp = create_type("error", 0, 0);
        a->ref = 0;
    }
    else if (a->kind == ":=")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if (!lexpr->ref)
            errornonreferenceableleft(a->line, lexpr->text);
        else if (lexpr->tp->kind == "procedure" || lexpr->tp->kind == "function" )
            errornonreferenceableleft(a->line, lexpr->text);
        else if (lexpr->tp->kind != "error" && rexpr->tp->kind != "error"
                 && !equivalent_types(lexpr->tp, rexpr->tp))
            errorincompatibleassignment(a->line);
        else
            a->tp = lexpr->tp;
    }
    /* examen */
    else if (a->kind == "*=")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        if (!lexpr->ref)
            errornonreferenceableleft(a->line, lexpr->text);
        else if (lexpr->tp->kind == "procedure" || lexpr->tp->kind == "function" )
            errornonreferenceableleft(a->line, lexpr->text);
        else if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "array") ||
            (lexpr->tp->down->kind != "error" && lexpr->tp->down->kind != "int") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "int"))
            errorincompatibleassignment(a->line);
        else
            a->tp = lexpr->tp;
    }
    else if (a->kind == "++")
    {
        AST *lexpr = child(a, 0);
        AST *rexpr = child(a, 1);
        TypeCheck(lexpr, "expression");
        TypeCheck(rexpr, "expression");
        a->tp = create_type("error", 0, 0);
        if ((lexpr->tp->kind != "error" && lexpr->tp->kind != "array") ||
            (rexpr->tp->kind != "error" && rexpr->tp->kind != "array"))
        {
            errorincompatibleoperator(a->line, a->kind);
        }
        else if (lexpr->tp->kind == "array" &&
                 rexpr->tp->kind == "array")
        {
            if (!equivalent_types(lexpr->tp->down, rexpr->tp->down))
            {
                errorincompatibleoperator(a->line, a->kind);
            }
            else
            {
                a->tp = create_type("array", 0, 0);
                a->tp->numelemsarray = lexpr->tp->numelemsarray + rexpr->tp->numelemsarray;
                a->tp->down = lexpr->tp->down;
            }
        }
        a->ref = 0;
    }
    /* fiexamen */
    else if (isbasickind(a->kind))
    {
        a->tp = create_type(a->kind, 0, 0);
        a->ref = 0;
    }
    else if (a->kind == "struct")
    {
        construct_struct(a);
    }
    else if (a->kind == "array")
    {
        construct_array(a);
    }
    else if (a->kind == "if")
    {
        AST *condition = child(a, 0);
        AST *instrt = child(a, 1);
        AST *instre = child(a, 2);
        TypeCheck(condition, "expression");
        if (condition->tp->kind != "error" && condition->tp->kind != "bool")
            errorbooleanrequired(a->line, a->kind);
        TypeCheck(instrt, "instruction");
        TypeCheck(instre, "instruction");
    }
    else if (a->kind == "while")
    {
        AST *condition = child(a, 0);
        AST *instr = child(a, 1);
        TypeCheck(condition, "expression");
        TypeCheck(instr, "instruction");
        if (condition->tp->kind != "error" && condition->tp->kind != "bool")
            errorbooleanrequired(a->line, a->kind);
    }
    else if (a->kind == "writeln" || a->kind == "write")
    {
        AST *expr = child(a, 0);
        TypeCheck(expr, "expression");
        if (expr->tp->kind != "error" && !isbasickind(expr->tp->kind))
            errorreadwriterequirebasic(a->line, a->kind);
    }   
    else if (a->kind == "read")
    {
        AST *expr = child(a, 0);
        TypeCheck(expr, "expression");
        if (expr->tp->kind != "error" && !expr->ref)
            errornonreferenceableexpression(a->line, a->kind);
        else if (expr->tp->kind != "error" && !isbasickind(expr->tp->kind))
            errorreadwriterequirebasic(a->line, a->kind);
    }   
    else if (a->kind == "procedure" || a->kind == "function")
    {
        AST *dec = child(a, 0);
        AST *params = child(dec, 0);
        AST *vars = child(a, 1);
        AST *blocks = child(a, 2);
        AST *instr = child(a, 3);
        AST *ret = child(a, 4);
        a->sc = symboltable.push();
        insert_params(params);
        insert_vars(vars);
        insert_headers(blocks);
        TypeCheck(blocks);
        TypeCheck(instr, "instruction");
        if (a->kind == "function" && ret != 0)
        {
            TypeCheck(ret);
            //if (debug) { cout << "return " << dec->tp->right->kind << "?" << ret->tp->kind << endl;}//FIXME
            if (!equivalent_types(ret->tp, dec->tp->right))
                errorincompatiblereturn(ret->line);
        }
        //if (debug) symboltable.write();//FIXME
        symboltable.pop();
    }
    else if (a->kind == "program")
    {
        AST *vars = child(a, 0);
        AST *blocks = child(a, 1);
        AST *instr = child(a, 2);
        a->sc = symboltable.push();
        insert_vars(vars);  // Afegeix ptypes de variables a la taula
        insert_headers(blocks);  // Afegeix ptypes de headers a la taula
        TypeCheck(blocks);
        TypeCheck(instr, "instruction");
        //if (debug) symboltable.write();//FIXME
        symboltable.pop();
    }
    else
    {
        cout << "BIG SEMANTIC PROBLEM! No case defined for kind " << a->kind << endl;
    }
    //cout<<"Ending with node \""<<a->kind<<"\""<<endl;
}
